using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using System.Collections.ObjectModel;
using Tomahawk.Runtime.Rendering.Objects;
using Tomahawk.Runtime.Rendering.Interfaces;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Tomahawk.Runtime.Rendering;
using Tomahawk.Runtime.Rendering.Materials;

namespace Tomahawk.Runtime.Scene
{
    /// <summary>
    /// This class an object that can be placed in the scene.
    /// </summary>
    public class SceneNode
    {
        protected SceneNode parentSceneNode = null;

        private Collection<SceneNode> childSceneNodes;

        protected Entity attachedEntity = null;

        private bool visible = true;

        private string renderGroupNames = "";
        private SASEffect.RenderGroupFlags renderGroups = SASEffect.RenderGroupFlags.Color;

        #region "Public Properties"

        public bool Visible
        {
            get { return visible; }
            set { visible = value; }
        }

        public SASEffect.RenderGroupFlags RenderGroups 
        { 
            get { return renderGroups; } 
        }

        public string RenderGroupNames
        {
            get { return renderGroupNames; }
            set
            {
                renderGroups = SASEffect.RenderGroupFlags.None;
                renderGroupNames = value;

                string[] tokens = value.Split(new char[] { '|' });
                foreach (string token in tokens)
                {
                    if (string.Compare(token, "COLOR", true) == 0)
                    {
                        renderGroups |= SASEffect.RenderGroupFlags.Color;
                    }
                    else if (string.Compare(token, "CANVAS", true) == 0)
                    {
                        renderGroups |= SASEffect.RenderGroupFlags.Canvas;
                    }
                    else if (string.Compare(token, "DEPTH", true) == 0)
                    {
                        renderGroups |= SASEffect.RenderGroupFlags.Depth;
                    }
                    else if (Engine.Instance.RenderManager.GenerateRenderGroupId != null)
                    {
                        renderGroups |= (SASEffect.RenderGroupFlags)(1 << (int)Engine.Instance.RenderManager.GenerateRenderGroupId(token));
                    }
                }
            }
        }

        private bool dirtyPosition = true;
        private Vector3 position = Vector3.Zero;
        public Vector3 Position { get { return position; } set { position = value; dirtyPosition = true; } }

        private bool dirtyScale = true;
        private Vector3 scale = Vector3.One;
        public Vector3 Scale { get { return scale; } set { scale = value; dirtyScale = true; } }

        private bool dirtyRotation = true;
        private Quaternion rotation = Quaternion.Identity;
        public Quaternion Rotation { get { return rotation; } set { rotation = value; dirtyRotation = true; } }

        private Matrix worldMatrix = Matrix.Identity;
        public Matrix WorldMatrix { get { return worldMatrix; } }

        private Matrix localMatrix = Matrix.Identity;
        public Matrix LocalMatrix { get { return localMatrix; } }

        /// <summary>
        /// Allow access to the attached entity
        /// </summary>
        public Entity AttachedEntity
        {
            get { return this.attachedEntity; }
        }

        /// <summary>
        /// Allow access to the parent scene node
        /// </summary>
        public SceneNode Parent
        {
            get { return this.parentSceneNode; }
        }

        public enum RenderOrders
        {
            ParentFirst,
            ChildrenFirst
        }

        public RenderOrders RenderOrder { get; set; }

        public enum VisitOrders
        {
            ParentFirst,
            ChildrenFirst
        }

        public VisitOrders VisitOrder { get; set; }

        #endregion

        #region "Root Scene Node Utility"

        /// <summary>
        /// Flag indicating the root scene node was already created
        /// </summary>
        static bool rootSceneNodeCreated = false;

        /// <summary>
        /// Create the root scene node instance 
        /// </summary>
        internal static SceneNode CreateRootSceneNode()
        {
            if (!rootSceneNodeCreated)
            {
                rootSceneNodeCreated = true;
                return new SceneNode();
            }
            else
                throw new Exception("Root Scene Node already created!");
        }

        #endregion

        /// <summary>
        /// Protected Constructor, avoid freely creation of scenenodes
        /// </summary>
        protected SceneNode()
        {
            //create the child collection
            this.childSceneNodes = new Collection<SceneNode>();

            //default render order
            this.RenderOrder = RenderOrders.ParentFirst;

            //default visit order
            this.VisitOrder = VisitOrders.ParentFirst;
        }

        #region "Child Scene Nodes"

        /// <summary>
        /// Create a child scene node
        /// </summary>
        /// <returns></returns>
        public SceneNode CreateChildSceneNode()
        {
            SceneNode node = new SceneNode();
            node.parentSceneNode = this;
            this.addChildSceneNode(node);
            return node;
        }

        /// <summary>
        /// Create a child scene node and registers it in the SceneManager nodelist
        /// </summary>
        public SceneNode CreateChildSceneNode(string nodeName)
        {
            SceneNode node = CreateChildSceneNode();
            Engine.Instance.SceneManager.RegisterNode(node, nodeName);
            return node;
        }

        /// <summary>
        /// Allow acces to ChildSceneNode from derived classes
        /// </summary>
        internal void addChildSceneNode(SceneNode node)
        {
            this.childSceneNodes.Add(node);
        }

        /// <summary>
        /// Allow acces to ChildSceneNode from derived classes
        /// </summary>
        internal void removeChildSceneNode(SceneNode node)
        {
            this.childSceneNodes.Remove(node);
        }

        #endregion

        /// <summary>
        /// Associate a renderable object to this node
        /// </summary>
        public void Attach(Entity entity)
        {
            //if other entity was attached, detach
            if (attachedEntity != null)
            {
                attachedEntity.setParentSceneNode(null);
            }

            //attach the entity
            entity.setParentSceneNode(this);
            attachedEntity = entity;
        }

        #region "Rendering"

        private bool isBillboard = false;

        /// <summary>
        /// Allow controlling the creation of the world matrix
        /// to align the rotation against the camera position
        /// </summary>
        public bool IsBillBoard
        {
            get { return this.isBillboard; }
            set { this.isBillboard = value; }
        }

        /// <summary>
        /// Render all children
        /// </summary>
        public virtual void Render(GraphicsDevice device, bool renderChildren)
        {
            if (this.RenderOrder == RenderOrders.ParentFirst)
            {   
                //render itself
                if (this.visible)
                {
                    //render the entity
                    IRenderable renderableEntity = this.attachedEntity as IRenderable;
                    if (renderableEntity != null)
                        renderableEntity.Render(ref worldMatrix, device);
                }

                //render all children
                if (renderChildren)
                {
                    for (int nodeId = 0; nodeId < this.childSceneNodes.Count; ++nodeId)
                    {
                        SceneNode child = this.childSceneNodes[nodeId];
                        if (child.visible) 
                            child.Render(device, renderChildren);
                    }
                }
            }
            else
            {
                //render all children
                if (renderChildren)
                {
                    for (int nodeId = 0; nodeId < this.childSceneNodes.Count; ++nodeId)
                    {
                        SceneNode child = this.childSceneNodes[nodeId];
                        if (child.visible)
                            child.Render(device, renderChildren);
                    }
                }

                //render itself
                if (this.visible)
                {
                    //render the entity
                    IRenderable renderableEntity = this.attachedEntity as IRenderable;
                    if (renderableEntity != null)
                        renderableEntity.Render(ref worldMatrix, device);
                }
            }
        }

        /// <summary>
        /// Visit all children
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="flags"></param>
        internal void Visit(SceneManager.VisitSceneDelegate callback, SceneManager.VisitSceneFlags flags)
        {
            if (flags == SceneManager.VisitSceneFlags.None)
            {
                return;
            }

            if (this.VisitOrder == VisitOrders.ParentFirst)
            {
                //visit itself
                if ((flags & SceneManager.VisitSceneFlags.AllNodes) != 0 ||
                    this.visible && (flags & SceneManager.VisitSceneFlags.AllVisibleNodes) != 0 ||
                    this.attachedEntity is IRenderable && (flags & SceneManager.VisitSceneFlags.AllRenderableNodes) != 0)
                {
                    if (!callback(this))
                    {
                        return;
                    }
                }

                //visit all children
                for (int nodeId = 0; nodeId < this.childSceneNodes.Count; ++nodeId)
                {
                    this.childSceneNodes[nodeId].Visit(callback, flags);
                }
            }
            else
            {
                //visit all children
                for (int nodeId = 0; nodeId < this.childSceneNodes.Count; ++nodeId)
                {
                    this.childSceneNodes[nodeId].Visit(callback, flags);
                }

                //visit itself
                if ((flags & SceneManager.VisitSceneFlags.AllNodes) != 0 ||
                    this.visible && (flags & SceneManager.VisitSceneFlags.AllVisibleNodes) != 0 ||
                    this.attachedEntity is IRenderable && (flags & SceneManager.VisitSceneFlags.AllRenderableNodes) != 0)
                {
                    if (!callback(this))
                    {
                        return;
                    }
                }
            }
        }

        //temp vars to optimize calculations
        private Matrix _scaleMatrix;
        private Matrix _rotMatrix;
        private Matrix _transMatrix;
        private Matrix _rotScaleMatrix;

        /// <summary>
        /// Compute the local matrix and set the flags off
        /// </summary>
        protected bool computeLocalMatrix()
        {
            bool dirtyRotationScale = this.dirtyRotation || this.dirtyScale;
            bool dirtyLocal = dirtyRotationScale || dirtyPosition;

            if (this.dirtyRotation)
            {
                Matrix.CreateFromQuaternion(ref this.rotation, out _rotMatrix);
                this.dirtyRotation = false;
            }

            if (this.dirtyPosition)
            {
                Matrix.CreateTranslation(ref this.position, out _transMatrix);
                this.dirtyPosition = false;
            }

            if (this.dirtyScale)
            {
                Matrix.CreateScale(ref this.scale, out _scaleMatrix);
                this.dirtyScale = false;
            }

            if (dirtyRotationScale)
            {
                Matrix.Multiply(ref _rotMatrix, ref _scaleMatrix, out _rotScaleMatrix);
            }

            if (dirtyLocal)
            {
                Matrix.Multiply(ref _rotScaleMatrix, ref _transMatrix, out this.localMatrix);
            }

            return dirtyLocal;
        }

        /// <summary>
        /// Update the scene node matrixes
        /// </summary>
        public void Update(ref Matrix parentWorldMatrix, bool dirtyParent)
        {
            //if local matrix update needed, recalculate the matrix
            bool dirtyLocal = computeLocalMatrix();

            //if we are a billboard, transform the matrix
            if (!this.isBillboard)
            {
                //compute the new world matrix
                if (dirtyLocal || dirtyParent)
                    Matrix.Multiply(ref this.localMatrix, ref parentWorldMatrix, out this.worldMatrix);
            }
            else
            {
                if (dirtyLocal || dirtyParent)
                    Matrix.Multiply(ref this.localMatrix, ref parentWorldMatrix, out this.worldMatrix);

                Camera cam = Engine.Instance.CameraManager.ActiveCamera;
                this.worldMatrix = _scaleMatrix *
                    Matrix.CreateConstrainedBillboard(this.WorldMatrix.Translation, cam.EyePosition, cam.YAxis, null, null);
            }

            //update children
            int childCount = this.childSceneNodes.Count;
            for (int nodeId = 0; nodeId < childCount; ++nodeId)
            {
                this.childSceneNodes[nodeId].Update(ref this.worldMatrix, dirtyLocal || dirtyParent);
            }
        }

        #endregion

        /// <summary>
        /// Remove this node and their children from the scene tree
        /// </summary>
        public void Destroy()
        {
            //destroy all childrens
            for (int nodeId = 0; nodeId < this.childSceneNodes.Count; ++nodeId)
            {
                this.childSceneNodes[nodeId].Destroy();
            }

            //remove this if in the list
            Engine.Instance.SceneManager.UnregisterNode(this);

            //remove all children
            this.childSceneNodes.Clear();
        }

        /// <summary>
        /// Changes the paren scene object
        /// </summary>
        internal void SetParent(SceneNode sceneNode)
        {
            this.parentSceneNode.removeChildSceneNode(this);
            this.parentSceneNode = sceneNode;
            this.parentSceneNode.addChildSceneNode(this);
        }

        /// <summary>
        /// Visibility change with propagation
        /// </summary>
        internal void setVisible(bool value, bool propagate)
        {
            this.visible = value;
            if (propagate)
            {

                for (int i = 0; i < this.childSceneNodes.Count; i++)
                {
                    this.childSceneNodes[i].setVisible(value, propagate);
                }
            }
        }

    }

}


